package controller;

import java.util.Observable;
import java.util.Observer;

import model.Accumulator;
import model.CodeLine;
import model.Command;
import model.CommandParameter;
import model.History;
import model.Reference;

public class RuntimeController implements Observer {
	private static final RuntimeController instance = new RuntimeController();

	private CodeController codeController = CodeController.getInstance();
	private MemoryController memory;
	private History history;

	private Timer timer;
	private Accumulator accumulator;

	private int sleep_time = 1000;

	private class Timer extends Thread {
		private boolean forward = true;
		private boolean doRun = true;

		private void setForward(boolean f) {
			forward = f;
		}

		@Override
		public void start() {
			doRun = true;
			super.start();
		}

		@Override
		public void run() {
			while (doRun) {
				try {
					if (forward) {
						playOneStep();
					} else {
						rewindOneStep();
					}
					Thread.sleep(sleep_time);
				} catch (InterruptedException e) {
					// not that bad
				}
			}
		}

		public void stopIt() {
			doRun = false;
		}
	}

	private RuntimeController() {
		accumulator = Accumulator.getInstance();
		history = new History();
		memory = MemoryController.getInstance();
		codeController.addObserver(this);
		update(null, null);
	}

	public static RuntimeController getInstance() {
		return instance;
	}

	public RuntimeState getRuntimeState() {
		return history.getCurrent();
	}

	public void setSpeed(int val) {
		sleep_time = val;
	}

	public void play() {
		stop();
		timer = new Timer();
		timer.setForward(true);
		timer.start();
	}

	public void rewind() {
		stop();
		timer = new Timer();
		timer.setForward(false);
		timer.start();
	}

	public void playOneStep() {
		RuntimeState runtimeState = RuntimeState.buildCurrentRuntimeState();
		// Change to the next state.. execute can change this
		runtimeState.setPointer(runtimeState.getPointer() + 1);
		if (history.hasNext()) {
			runtimeState = history.next();
			applyRuntimeState(runtimeState);
		} else {
			CodeLine next = memory.getCodeLine();
			if (next != null && next instanceof Command) {
				Reference[] refs = new Reference[next.getParameterCount()];
				int i = 0;
				for (CommandParameter ref : next.getCommandParameter()) {
					refs[i++] = (Reference) ref;
				}
				((Command) next).execute(runtimeState, refs);
				history.add(runtimeState);
				applyRuntimeState(runtimeState);
			} else if (next != null) {
				history.add(runtimeState);
				applyRuntimeState(runtimeState);
				playOneStep();
			} else if (next == null) {
				stop();
			}
		}

	}

	public void rewindOneStep() {
		if (history.getCurrent().getPointer() == 0) {
			if (timer != null) {
				stop();
			}
		}
		applyRuntimeState(history.back());
	}

	public void stop() {
		if (timer != null) {
			timer.stopIt();
		}
		timer = null;
	}

	private void applyRuntimeState(RuntimeState state) {
		MemoryController.getInstance().setPointer(state.getPointer());
		MemoryController.getInstance().setRegister(state.getMemory());
		accumulator.setValue(state.getAccumulator());
	}

	@Override
	public void update(Observable o, Object arg) {
		stop();
		RuntimeState state = RuntimeState.buildCurrentRuntimeState();
		state.setAccumulator(0);
		state.setPointer(0);
		history.reset();
		applyRuntimeState(state);
	}

}
